package cn.cosmosx.base.util;

import cn.cosmosx.base.dict.ApiDict;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 标准Gson序列化/反序列工具
 *
 * 支持自定义枚举类型，定义的枚举必须实现{@linkplain cn.cosmosx.base.dict.ApiDict}接口
 *
 * 支持处理嵌套集合对象
 *
 * 为避免报文与Java Bean的属性不匹配，建议搭配注解{@linkplain com.google.gson.annotations.SerializedName}一起使用
 * 该注解支持多种备选的属性名称备选方案，因此，可完美解决多端同一个字段而报文中字段名不一致的问题：
 * 即Java Bean中是同一个属性，但报文中会出现不同名称。
 * 样例：
 * <pre>
 * <code>
 *  class User {
 *     @com.google.gson.annotations.SerializedName(value = "user_name", alternate = {"userName", "USER_NAME"})
 *     private String username;
 *  }
 * </code>
 * 在序列化时，报文中的user_name，userName，USER_NAME均可反序列化到User#username
 * </pre>
 */
@Slf4j
public final class GsonUtil {

    public static Gson gson() {
        return new GsonBuilder().disableHtmlEscaping()
                .serializeNulls()
                .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.TRANSIENT, Modifier.NATIVE, Modifier.PROTECTED)
                .enableComplexMapKeySerialization()
                .disableJdkUnsafe()
                .disableHtmlEscaping()
                .registerTypeAdapterFactory(new ListTypeAdapterFactory())
                .registerTypeAdapterFactory(new ApiDictEnumTypeAdapterFactory())
                .create();
    }

    /**
     * 通用List集合序列化/反序列化适配器
     */
    private static class ListTypeAdapterFactory implements TypeAdapterFactory {
        @SuppressWarnings("all")
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
            Type type = typeToken.getType();
            if (!List.class.isAssignableFrom(typeToken.getRawType()) || !(type instanceof ParameterizedType)) {
                return null;
            }
            // 拿到List泛型的真正类型
            Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
            TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
            // 返回一个新的 TypeAdapter 对象，用于处理嵌套的 List 对象
            return new ListTypeAdapter(elementAdapter).nullSafe();
        }

        /**
         * 用于处理嵌套 List 对象的 TypeAdapter。
         */
        private static class ListTypeAdapter<E> extends TypeAdapter<List<E>> {
            private final TypeAdapter<E> typeAdapter;

            private ListTypeAdapter(TypeAdapter<E> typeAdapter) {
                this.typeAdapter = typeAdapter;
            }

            @Override
            public void write(JsonWriter out, List<E> list) throws IOException {
                if (list == null) {
                    out.nullValue();
                    return;
                }
                out.beginArray();
                for (E e : list) {
                    typeAdapter.write(out, e);
                }
                out.endArray();
            }

            @Override
            public List<E> read(JsonReader in) throws IOException {
                JsonToken token = in.peek();
                if (token == JsonToken.NULL) {
                    in.nextNull();
                    return null;
                }
                // 以Java Bean的数据结构为准，如果JSON字符串中是一个对象，但Java Bean中对应的是集合
                // 则需要将 JSON字符串中的对象序列化放进集合返回，保持数据格式统一（必须做兼容性处理）
                List<E> list = new ArrayList<>();
                if (token == JsonToken.BEGIN_OBJECT) {
                    E e = typeAdapter.read(in);
                    list.add(e);
                } else if (token == JsonToken.BEGIN_ARRAY) {
                    in.beginArray();
                    while (in.hasNext()) {
                        E e = typeAdapter.read(in);
                        list.add(e);
                    }
                    in.endArray();
                }
                return list;
            }
        }
    }

    /**
     * 通用自定义枚举序列化/反序列化适配器
     */
    public static class ApiDictEnumTypeAdapterFactory implements TypeAdapterFactory {
        @SuppressWarnings("all")
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            Class<?> rawType = type.getRawType();
            if (!rawType.isEnum() || !ApiDict.class.isAssignableFrom(rawType)) {
                return null;
            }
            return new ApiDictAdapter<>((Class<T>) rawType);
        }
    }

    private static class ApiDictAdapter<T> extends TypeAdapter<T> {
        private final Class<T> enumType;
        private final Map<Object, T> dictMap = new HashMap<>();

        public ApiDictAdapter(Class<T> enumType) {
            this.enumType = enumType;
            Arrays.stream(enumType.getEnumConstants()).forEach(e -> dictMap.put(((ApiDict<?, ?>) e).getCode(), e));
        }

        @Override
        public void write(JsonWriter out, T value) throws IOException {
            if (value == null) {
                out.nullValue();
            } else {
                out.value((String) ((ApiDict<?, ?>) value).getCode());
            }
        }

        @Override
        public T read(JsonReader in) throws IOException {
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                return null;
            } else {
                String value = in.nextString();
                T dict = dictMap.get(value);
                return Optional.ofNullable(dict)
                        .orElseThrow(() -> new IOException("value[" + value + "] does not defined in[" + enumType + "]"));
            }
        }
    }
}
